/*==========================================================================*\
| $Id: PathUtils.java,v 1.2 2009/10/26 14:35:54 aallowat Exp $
|*-------------------------------------------------------------------------*|
| Copyright (C) 2009 Virginia Tech
|
| This file is part of the Web-CAT CxxTest Distribution.
|
| Web-CAT is free software; you can redistribute it and/or modify
| it under the terms of the GNU Affero General Public License as published
| by the Free Software Foundation; either version 3 of the License, or
| (at your option) any later version.
|
| Web-CAT is distributed in the hope that it will be useful,
| but WITHOUT ANY WARRANTY; without even the implied warranty of
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
| GNU General Public License for more details.
|
| You should have received a copy of the GNU Affero General Public License
| along with Web-CAT; if not, see <http://www.gnu.org/licenses/>.
\*==========================================================================*/
package net.sf.webcat.cxxtest.generator;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
//--------------------------------------------------------------------------
/**
* Utility functions for working with relative paths.
*
* @author Tony ALlevato
* @version $Id: PathUtils.java,v 1.2 2009/10/26 14:35:54 aallowat Exp $
*/
public class PathUtils
{
//~ Methods ...............................................................
// ----------------------------------------------------------
/**
* Gets a relative path from the file or directory "source" to the file or
* directory "destination". For example, if source is "/a/b/c" and
* destination is "/a/d/e/x.txt" (assuming that "/a/b/c" is a directory),
* then this function returns the string "../../d/e/x.txt".
*
* @param source the source path, as a String. If null, the process's
* current directory is used. The source can be either a directory or a
* file.
* @param destination the destination path, as a String. Cannot be null.
*
* @return the relative path from source to destination, as a String
*/
public static String relativizePath(String source, String destination)
{
File sourceFile;
if (source == null)
{
sourceFile = new File(".");
}
else
{
sourceFile = new File(source);
}
return relativizePath(sourceFile, new File(destination));
}
// ----------------------------------------------------------
/**
* Gets a relative path from the file or directory "source" to the file or
* directory "destination". For example, if source is "/a/b/c" and
* destination is "/a/d/e/x.txt", then this function returns the string
* "../../d/e/x.txt".
*
* @param source the source path, as a File. If null, the process's current
* directory is used
* @param destination the destination path, as a File. Cannot be null.
*
* @return the relative path from source to destination, as a String
*/
public static String relativizePath(File source, File destination)
{
if (destination == null)
{
throw new IllegalArgumentException("The destination path cannot "
+ "be null.");
}
if (source != null && !source.isDirectory())
{
// If the source is a file, use the directory that contains it.
source = source.getParentFile();
}
if (source == null)
{
// Use the process's current working directory if the source is
// null.
source = new File(".");
}
String[] srcSegments = reversedPathSegments(source);
String[] destSegments = reversedPathSegments(destination);
if (srcSegments == null || destSegments == null)
{
// If there was a problem segmenting either of the paths, just
// play it safe and return the full destination path.
return destination.getAbsolutePath();
}
else
{
// This is a special check for Windows systems, to make sure that
// both paths are on the same volume (if they don't, then there is
// no relative path between them). If the paths are not on the same
// volume, just return the full destination path.
if (IS_WINDOWS)
{
String srcVolume = srcSegments[srcSegments.length - 1];
String destVolume = destSegments[destSegments.length - 1];
if (!srcVolume.equalsIgnoreCase(destVolume))
{
return destination.getAbsolutePath();
}
}
return relativizePathSegments(srcSegments, destSegments);
}
}
// ----------------------------------------------------------
/**
* Separates a path into its individual segments and returns these segments
* in a list, in reverse order. For example, the path "/a/b/c/d.txt" will
* be returned as the list [ "d.txt", "c", "b", "a" ].
*
* @param path the input path
* @return a List containing the path segments in reverse order
*/
private static String[] reversedPathSegments(File path)
{
List<String> segments = new ArrayList<String>();
try
{
// Canonicalize the path so that both will compare with string
// equality later.
File currentFile = path.getCanonicalFile();
// Navigate up to the root, pruning off each segment of the path.
while (currentFile != null)
{
if (currentFile.getParentFile() == null && IS_WINDOWS)
{
String volume = currentFile.getAbsolutePath();
if (volume.endsWith(File.separator))
{
volume = volume.substring(0,
volume.length() - File.separator.length());
}
segments.add(volume);
}
else
{
segments.add(currentFile.getName());
}
currentFile = currentFile.getParentFile();
}
}
catch (IOException e)
{
e.printStackTrace();
segments = null;
}
if (segments != null)
{
return segments.toArray(new String[segments.size()]);
}
else
{
return null;
}
}
// ----------------------------------------------------------
/**
* Computes a string containing the relative path from the directory or
* file represented by the source segments to the directory or file
* represented by the destination segments.
*
* @param srcSegments the segments of the source path
* @param destSegments the segments of the destination path
*/
private static String relativizePathSegments(String[] srcSegments,
String[] destSegments)
{
int srcIndex = srcSegments.length - 1;
int destIndex = destSegments.length - 1;
StringBuffer relativePath = new StringBuffer();
// Skip past the parts of the paths that both have in common. String
// equality suffices here since the paths are canonicalized before
// being passed into this function.
while (srcIndex >= 0 && destIndex >= 0 &&
srcSegments[srcIndex].equals(destSegments[destIndex]))
{
srcIndex--;
destIndex--;
}
// For each remaining segment in the source path, add a reference to
// the parent directory.
for (; srcIndex >= 0; srcIndex--)
{
relativePath.append("..");
relativePath.append(File.separator);
}
// Then, now that we're at the location where the remainder of the
// destination path starts, append those segments to the end of the
// path.
for (; destIndex > 0; destIndex--)
{
relativePath.append(destSegments[destIndex]);
relativePath.append(File.separator);
}
// Finally, append the filename, if there is one.
if (destIndex == 0)
{
relativePath.append(destSegments[destIndex]);
}
// Remove a final trailing slash if it is there.
if (relativePath.charAt(relativePath.length() - 1) ==
File.separatorChar)
{
relativePath.deleteCharAt(relativePath.length() - 1);
}
return relativePath.toString();
}
//~ Static initialization .................................................
// ----------------------------------------------------------
static
{
// Determine if we're running on Windows, so we can do a volume check
// if necessary.
String osName = System.getProperty("os.name").toLowerCase();
IS_WINDOWS = osName.contains("windows");
}
//~ Static/instance variables .............................................
private static final boolean IS_WINDOWS;
}